package fr.n7.khome.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import fr.n7.khome.dao.LodgingDAO;
import fr.n7.khome.model.Lodging;
import fr.n7.khome.model.LodgingFieldName;
import fr.n7.khome.model.LodgingType;
/**
 * It implements the request a user want to do.
 * It contains the ideal lodging (constructed from data user has typed - the pattern)
 * and the importance of each criterion (price,type,liveSurface,fieldSurface,
 * ageImportance and work to realize).
 * 
 * Thanks to getPatternMatchingResult, it is possible to get the results from
 * the database that corresponds to lodgings that best match.
 * 
 * @author bbbaccot && clej37
 *
 */
public class Request {

	/**
	 * The "ideal" (or requested) lodging (the pattern)
	 */
	private Lodging requestLodging;
	
	private int priceImportance;
	private int typeImportance;
	private int liveSurfaceImportance;
	private int fieldSurfaceImportance;
	private int ageImportance;
	private int workPricePCImportance;

	/**
	 * Create a Request from all the fuzzy numbers of a lodging and
	 * the importance of each field. 
	 * 
	 */
	public Request(
			Fuzzy price, int priceImportance,
			LodgingType type, int typeImportance,
			Fuzzy fieldSurface, int fieldSurfaceImportance,
			Fuzzy liveSurface, int liveSurfaceImportance,
			Fuzzy age, int ageImportance,
			Fuzzy workPricePC, int workPricePCImportance) {
		this.requestLodging.setPrice(price);
		this.priceImportance = priceImportance;
		this.requestLodging.setType(type);
		this.typeImportance = typeImportance;
		this.requestLodging.setLiveSurface(liveSurface);
		this.liveSurfaceImportance = liveSurfaceImportance;
		this.requestLodging.setFieldSurface(fieldSurface);
		this.fieldSurfaceImportance = fieldSurfaceImportance;
		this.requestLodging.setAge(age);
		this.ageImportance = ageImportance;
		this.requestLodging.setWorkPricePC(workPricePC);
		this.workPricePCImportance = workPricePCImportance;
	}
	
	/**
	 * Create a Request from a requested lodging (the pattern) and the importance
	 * of each field.
	 * 
	 */
	public Request(Lodging lodging,
			int priceImportance,
			int typeImportance,
			int fieldSurfaceImportance,
			int liveSurfaceImportance,
			int ageImportance,
			int workPricePCImportance){
		this.requestLodging = lodging;
		this.priceImportance = priceImportance;
		this.typeImportance = typeImportance;
		this.liveSurfaceImportance = liveSurfaceImportance;
		this.fieldSurfaceImportance = fieldSurfaceImportance;
		this.ageImportance = ageImportance;
		this.workPricePCImportance = workPricePCImportance;
	}
	
	/**
	 * Create a Request from a requested lodging (the pattern) and the same importance
	 * for each field.
	 */
	public Request(Lodging lodging, int importance){
		this.requestLodging = lodging;
		this.priceImportance = importance;
		this.typeImportance = importance;
		this.liveSurfaceImportance = importance;
		this.fieldSurfaceImportance = importance;
		this.ageImportance = importance;
		this.workPricePCImportance = importance;
	}

	/**
	 * Put an object to a specific field of a lodging.
	 * @param fieldName - the name of the field
	 * @param o - the object to affect to this field.
	 */
	public void setField(LodgingFieldName fieldName, Object o){
		requestLodging.set(fieldName, o);
	}
	
	public Object getField(LodgingFieldName fieldName){
		return requestLodging.get(fieldName);
	}
	
	/**
	 * Set the importance of a field.
	 * @param fieldName - the name of a field
	 * @param i - the importance for this field
	 */
	public void setFieldImportance(LodgingFieldName fieldName, int i){
		switch(fieldName){
		case AGE : 				this.ageImportance 			= i; break;
		case FIELD_SURFACE : 	this.fieldSurfaceImportance = i; break;
		case LIVE_SURFACE : 	this.liveSurfaceImportance	= i; break;
		case PRICE : 			this.priceImportance		= i; break;
		case TYPE : 			this.typeImportance 		= i; break;
		case WORKPRICEPC : 		this.workPricePCImportance	= i; break;
		}
	}
	
	/**
	 * Get the importance of a field.
	 * @param fieldName - the name of the field
	 * @return the importance of the field
	 */
	public Integer getFieldImportance(LodgingFieldName fieldName){
		switch(fieldName){
		case AGE : 				return this.ageImportance;
		case FIELD_SURFACE : 	return this.fieldSurfaceImportance;
		case LIVE_SURFACE : 	return this.liveSurfaceImportance;
		case PRICE : 			return this.priceImportance;
		case TYPE : 			return this.typeImportance;
		case WORKPRICEPC : 		return this.workPricePCImportance;
		}
		return null;
	}

	/**
	 * Do a local pattern matching between each fields of the patterns and the data.
	 * Results are stored in a Result object for each lodging in the database.
	 * 
	 * @see fr.n7.khome.core.Result
	 * @param dao - a lodging database
	 * @return a list of sorted Results
	 */
	protected List<Result> patternMatchLocal(LodgingDAO dao) {
		List<Result> result = new ArrayList<Result>();
		Collection<Lodging> lodgings = dao.listAll();
		Result r = null;
		float likelihood,necessity;
		for(Lodging lodging : lodgings) {
			r = new Result(lodging);
			//Price
			likelihood = Fuzzy.getLikelihood(lodging.getPrice(),this.requestLodging.getPrice());
			necessity  = Fuzzy.getNecessity(lodging.getPrice(), this.requestLodging.getPrice());
			r.getLikelihood().put(LodgingFieldName.PRICE, likelihood);
			r.getNecessity().put(LodgingFieldName.PRICE, necessity);
			//Type : a bit strange => a "manual" hack
			if(lodging.getType() == LodgingType.WHATEVER
					|| requestLodging.getType() == LodgingType.WHATEVER
					|| requestLodging.getType() == lodging.getType()) {
				likelihood = 1;
				necessity  = 1;
			} else {
				likelihood = 0;
				necessity  = 0;
			}
			r.getLikelihood().put(LodgingFieldName.TYPE, likelihood);
			r.getNecessity().put(LodgingFieldName.TYPE, necessity);
			//LiveSurface
			likelihood = Fuzzy.getLikelihood(lodging.getLiveSurface(),this.requestLodging.getLiveSurface());
			necessity  = Fuzzy.getNecessity(lodging.getLiveSurface(), this.requestLodging.getLiveSurface());
			r.getLikelihood().put(LodgingFieldName.LIVE_SURFACE, likelihood);
			r.getNecessity().put(LodgingFieldName.LIVE_SURFACE, necessity);
			//FieldSurface
			likelihood = Fuzzy.getLikelihood(lodging.getFieldSurface(),this.requestLodging.getFieldSurface());
			necessity  = Fuzzy.getNecessity(lodging.getFieldSurface(), this.requestLodging.getFieldSurface());
			r.getLikelihood().put(LodgingFieldName.FIELD_SURFACE, likelihood);
			r.getNecessity().put(LodgingFieldName.FIELD_SURFACE, necessity);
			//Age
			likelihood = Fuzzy.getLikelihood(lodging.getAge(),this.requestLodging.getAge());
			necessity  = Fuzzy.getNecessity(lodging.getAge(), this.requestLodging.getAge());
			r.getLikelihood().put(LodgingFieldName.AGE, likelihood);
			r.getNecessity().put(LodgingFieldName.AGE, necessity);
			//WorkPricePC
			likelihood = Fuzzy.getLikelihood(lodging.getWorkPricePC(),this.requestLodging.getWorkPricePC());
			necessity  = Fuzzy.getNecessity(lodging.getWorkPricePC(), this.requestLodging.getWorkPricePC());
			r.getLikelihood().put(LodgingFieldName.WORKPRICEPC, likelihood);
			r.getNecessity().put(LodgingFieldName.WORKPRICEPC, necessity);
			//Finally
			result.add(r);
		}
		return result;
	}

	/**
	 * Get the pattern matching results.
	 * The return of this method is a sorted list of Results.
	 * 
	 * @param dao - the data list
	 * @return the sorted list of results
	 */
	public Collection<Result> getPatternMatchingResults(LodgingDAO dao) {
		List<Result> l = patternMatchLocal(dao);
		Collection<Result> results = new TreeSet<Result>();
		float glikeli,gnecess,sumnecess,sumlikeli;
		float p,q;
		int MAXW = getMaxImportance() > 0 ? getMaxImportance():1;
		for(Result r : l) {
			glikeli=1;
			gnecess=1;
			sumnecess=0;
			sumlikeli=0;
			for(int i=0;i<LodgingFieldName.values().length;i++) {
				LodgingFieldName f = LodgingFieldName.values()[i];
				float wi=0;
				switch(f) {
				case PRICE :
					wi = ((float)priceImportance)/MAXW;
					break;

				case TYPE :
					wi = ((float)typeImportance)/MAXW;
					break;

				case LIVE_SURFACE :
					wi = ((float)liveSurfaceImportance)/MAXW;
					break;

				case FIELD_SURFACE :
					wi = ((float)fieldSurfaceImportance)/MAXW;
					break;

				case AGE :
					wi = ((float)ageImportance)/MAXW;
					break;

				case WORKPRICEPC :
					wi = ((float)workPricePCImportance)/MAXW;
					break;
				}
				float li = r.getLikelihood().get(f);
				float ne = r.getNecessity().get(f);
				p = Math.max(1-wi,li);
				q = Math.max(1-wi,ne);
				if(p<glikeli) glikeli=p;
				if(q<gnecess) gnecess=q;
				sumlikeli += wi*li;
				sumnecess += wi*ne;
			}
			r.setGlikelihood(glikeli);
			r.setGnecessity(gnecess);
			r.setSumnecessity(sumnecess);
			r.setSumlikelihood(sumlikeli);
			results.add(r);
		}
		return results;
	}

	/**
	 * Get the max importance for the fields
	 */
	protected int getMaxImportance() {
		int max=0;
		LodgingFieldName[] fields = LodgingFieldName.values();
		for(int i=0;i<fields.length;i++) {
			if(getFieldImportance(fields[i])>max) max = getFieldImportance(fields[i]);
		}
		return max;
	}
	
	public String toString(){
		StringBuffer sb = new StringBuffer("Request :\n");
		sb.append("- price : "+this.requestLodging.getPrice()+" ("+this.priceImportance+")\n");
		sb.append("- type : "+this.requestLodging.getType()+" ("+this.typeImportance+")\n");
		sb.append("- liveSurface : "+this.requestLodging.getLiveSurface()+" ("+this.liveSurfaceImportance+")\n");
		sb.append("- fieldSurface : "+this.requestLodging.getFieldSurface()+" ("+this.fieldSurfaceImportance+")\n");
		sb.append("- age : "+this.requestLodging.getAge()+" ("+this.ageImportance+")\n");
		sb.append("- workPricePC : "+this.requestLodging.getWorkPricePC()+" ("+this.workPricePCImportance+")\n");
		return sb.toString();
	}
}
